package run;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Scope;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.messaging.converter.StringMessageConverter;
import org.springframework.messaging.simp.stomp.*;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.socket.*;
import org.springframework.web.socket.client.WebSocketClient;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.config.annotation.*;
import org.springframework.web.socket.messaging.WebSocketStompClient;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
import utils.Utils;

import javax.security.auth.Subject;
import java.io.IOException;
import java.lang.reflect.Type;
import java.security.Principal;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;

@Slf4j
@SpringBootTest(classes = StartUpForStompClient.Dummy.class,
        webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class StartUpForStompClient {

    public StartUpForStompClient() {
    }

    @Autowired
    GenericApplicationContext applicationContext;

    static RestTemplate template = null;

    @BeforeAll
    public static void setUp() {
        template = new RestTemplate();
    }

    public class CustomWebSocketHandler implements WebSocketHandler {
        @Autowired
        WebSocketStompClient stompClient;

        ConcurrentHashMap<String, MyStompSessionHandler> map = new ConcurrentHashMap<>();


        private final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(CustomWebSocketHandler.class);

        @Override
        public void afterConnectionEstablished(WebSocketSession session) throws Exception {

            log.info(String.format("ConnectionEstablished: %s,%s,%s,%s",
                    session.getId(), session.getLocalAddress(), session.getRemoteAddress(), session.getHandshakeHeaders().toString()));

            map.computeIfAbsent(session.getId(), id -> {
                log.info("map no have the sessionId:{}", id);
                MyStompSessionHandler bean = applicationContext.getBean(MyStompSessionHandler.class);
                log.info("bean:{}",bean);
                return bean;
            });
            MyStompSessionHandler myStompSessionHandler = map.get(session.getId());


            String url = "ws://127.0.0.1:8083/portfolio";

            HttpHeaders httpHeaders = new HttpHeaders();

            httpHeaders.set("Cookie", "Idea-8296eb30=d86bdee7-e00f-48c3-83fe-e0b84df74a4a; csrftoken=0LhpmoWVdWhTcxNSA2r5sEhs2UZeLlAYelJm4l6apaQ0dZbxoXNlACspJWWG7u4S; Pastease.passive.chance.kfzz9br7dC3Qwzl=chance77.0; Pastease.passive.activated.kfzz9br7dC3Qwzl=0; _ga=GA1.1.1398574246.1660881221; SESSION=715fc843-02f0-4ab5-afdd-6307b826fc99; JSESSIONID=BB682901F86D6D14A729968615D76435");
            WebSocketHttpHeaders webSocketHttpHeaders = new WebSocketHttpHeaders(httpHeaders);

            StompHeaders stompHeaders = new StompHeaders();
            stompHeaders.set("Cookie", "Idea-8296eb30=d86bdee7-e00f-48c3-83fe-e0b84df74a4a; csrftoken=0LhpmoWVdWhTcxNSA2r5sEhs2UZeLlAYelJm4l6apaQ0dZbxoXNlACspJWWG7u4S; Pastease.passive.chance.kfzz9br7dC3Qwzl=chance77.0; Pastease.passive.activated.kfzz9br7dC3Qwzl=0; _ga=GA1.1.1398574246.1660881221; SESSION=715fc843-02f0-4ab5-afdd-6307b826fc99; JSESSIONID=BB682901F86D6D14A729968615D76435");
            stompHeaders.setHeartbeat(new long[]{0, 0});
            // if session have more than one, then need to use  context.getBean(StompSessionHandler.class);,need to close the instance.
            ListenableFuture<StompSession> connect = stompClient.connect(url, webSocketHttpHeaders, stompHeaders, myStompSessionHandler);

            connect.addCallback((StompSession stompSession) -> {
                log.info("session:{}", stompSession.getSessionId());
            }, (Throwable throwable) -> {
                log.error("throwable:{}", throwable);
            });
        }

        @Override
        public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {

            log.info(String.format("handleMessage: %s,%s,%s,%s",
                    session.getId(), session.getLocalAddress(), session.getRemoteAddress(), session.getHandshakeHeaders().toString()));

            MyStompSessionHandler sessionHandler = map.get(session.getId());
            sessionHandler.getCountDownLatch().await();
           log.info("----------------------start handleMessage -------------------------------------------------------");
            String payload = (String) message.getPayload();
            AtomicReference<String> msg = new AtomicReference<>("");
            if (payload.equals("TradeMessageHandle")) {
                sessionHandler.handleTradeMessage((str)->{
                    msg.set((String) str);
                    TextMessage textMessage = new TextMessage(msg.get());
                    try {
                        session.sendMessage(textMessage);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
            } else if (payload.equals("FruitMessageHandle")) {
                sessionHandler.handleFruitMessage((str)->{
                    msg.set((String) str);
                    TextMessage textMessage = new TextMessage(msg.get());
                    try {
                        session.sendMessage(textMessage);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                });
            }


        }

        @Override
        public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
            log.error(String.format("%s--%s", session.getId(), exception.getMessage()));
            map.remove(session.getId());
        }

        @Override
        public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
            log.info(String.format("%s--%s", session.getId(), closeStatus.toString()));
            map.remove(session.getId());
        }

        @Override
        public boolean supportsPartialMessages() {
            return false;
        }
    }

    public class CustomWebSocketConfigurer implements WebSocketConfigurer {
        private CustomWebSocketHandler customWebSocketHandler;

        public CustomWebSocketConfigurer() {
        }

        public CustomWebSocketConfigurer(CustomWebSocketHandler customWebSocketHandler) {
            this.customWebSocketHandler = customWebSocketHandler;
        }

        @Override
        public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
            ServletWebSocketHandlerRegistration sockJsServiceRegistration = (ServletWebSocketHandlerRegistration) registry.addHandler(customWebSocketHandler, "/foo")
                    .addInterceptors(new HttpSessionHandshakeInterceptor());
            sockJsServiceRegistration.setAllowedOrigins("localhost:8084");
            SockJsServiceRegistration sockJsServiceRegistration1 = sockJsServiceRegistration.withSockJS();
            sockJsServiceRegistration1.setSupressCors(true);

        }
    }

    @EnableWebSocket
    @Import(value = {StartUpForStompClient.class, com.SpringApplication.class})
    @Configuration
    public class Dummy {
        @Bean
        public CustomWebSocketHandler customWebSocketHandler(){
            return new CustomWebSocketHandler();
        }

        @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
        @Bean
        public MyStompSessionHandler myStompSessionHandler() {
            return new MyStompSessionHandler();
        }
        @Bean
        public ThreadPoolTaskScheduler threadPoolTaskScheduler(){
            ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
            threadPoolTaskScheduler.setPoolSize(10);
            return threadPoolTaskScheduler;
        }
        @Bean
        public CustomWebSocketConfigurer customWebSocketConfigurer(CustomWebSocketHandler customWebSocketHandler) {
            return new CustomWebSocketConfigurer(customWebSocketHandler);
        }

        @Bean
        public WebSocketStompClient stompClient() {
            log.info("===========initials==================");
            WebSocketClient webSocketClient = new StandardWebSocketClient();
            WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
            stompClient.setMessageConverter(new StringMessageConverter());
            ThreadPoolTaskScheduler bean = applicationContext.getBean(ThreadPoolTaskScheduler.class);
            Assertions.assertNotNull(bean);
            stompClient.setTaskScheduler(bean);
            return stompClient;
        }
    }

    Object lock = new Object();


    @Test
    public void makeUpClient() throws InterruptedException, ExecutionException {
        Utils.print(applicationContext);
        synchronized (lock) {
            lock.wait();
        }
    }

    @Getter
    @Setter
    @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public class MyStompSessionHandler extends StompSessionHandlerAdapter {
        private StompSession stompSession;
        private List body;
        private CountDownLatch countDownLatch = new CountDownLatch(1);

        public StompSession getStompSession() {
            return stompSession;
        }

        public void handleTradeMessage(Consumer consumer) {

            if (body !=null && body.size() > 0 && stompSession!=null) {
                log.info("current user sessionId is{}", body);
                 subscribeUser(stompSession, body, "/app/trade", "/queue/foo-user%s", "0",consumer);
            }

        }

        public void handleFruitMessage(Consumer consumer) {
            if (body !=null &&  body.size() > 0  && stompSession!=null) {
                log.info("current user sessionId is{}", body);
                 subscribeUser(stompSession, body, "/app/fruit", "/queue/fruit-user%s", "1",consumer);
            }
        }

        @Override
        public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
            Set<Map.Entry<String, List<String>>> entries = connectedHeaders.entrySet();
            entries.stream().forEach(s -> {
                String key = s.getKey();
                List<String> val = s.getValue();
                log.info("key:{},val:{}", key, val.size() > 0 ? val.toArray(new String[0]) : "");
            });
            this.stompSession = session;

            LinkedMultiValueMap<String, String> linkedMultiValueMap = new LinkedMultiValueMap<>();
            linkedMultiValueMap.set("Cookie", "Idea-8296eb30=d86bdee7-e00f-48c3-83fe-e0b84df74a4a; csrftoken=0LhpmoWVdWhTcxNSA2r5sEhs2UZeLlAYelJm4l6apaQ0dZbxoXNlACspJWWG7u4S; Pastease.passive.chance.kfzz9br7dC3Qwzl=chance77.0; Pastease.passive.activated.kfzz9br7dC3Qwzl=0; _ga=GA1.1.1398574246.1660881221; SESSION=715fc843-02f0-4ab5-afdd-6307b826fc99; JSESSIONID=BB682901F86D6D14A729968615D76435");
            HttpEntity<Object> entity = new HttpEntity<>(linkedMultiValueMap);
            ResponseEntity<List> listResponseEntity = template.postForEntity("http://localhost:8083/currentUserSession", entity, List.class);
            this.body = listResponseEntity.getBody();
            log.info("----------------------------get sessionId-----------------------------{}",body);
            countDownLatch.countDown();;
        }


        public void subscribeUser(StompSession session, List<String> usersession, String sourceDestinate, String targetDestinate, String subScribeIndex, Consumer<String> consumer) {

            usersession.stream().forEach(sessionId -> {
                StompHeaders stompHeaders = new StompHeaders();
                stompHeaders.setDestination(sourceDestinate);
                session.send(stompHeaders, "helloworld!!!");
                stompHeaders.setDestination(String.format(targetDestinate, sessionId));
                stompHeaders.setId(subScribeIndex);
                Assertions.assertTrue(session instanceof DefaultStompSession);

                StompSession.Subscription subscribe = session.subscribe(stompHeaders, new StompFrameHandler() {
                    @Override
                    public Type getPayloadType(StompHeaders headers) {
                        return String.class;
                    }

                    @Override
                    public void handleFrame(StompHeaders headers, Object payload) {
                        log.info("msg:{},{}", headers.getDestination(), payload.toString());
                        String msgInfo = String.format("%s,%s", headers.getDestination(), payload.toString());
                        consumer.accept(msgInfo);
                    }
                });
            });

        }

    }

    @NoArgsConstructor
    @AllArgsConstructor
    public class CustomPrincipal implements Principal {
        private String name;

        @Override
        public String getName() {
            return name;
        }

        @Override
        public boolean implies(Subject subject) {
            return Principal.super.implies(subject);
        }
    }

    @Test
    public void verify() throws InterruptedException {

        synchronized (lock) {
            lock.wait();
        }
    }

}
